{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "initial_id",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:55.279317Z",
     "start_time": "2025-01-21T15:01:55.272959Z"
    },
    "collapsed": true,
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:34.400102Z",
     "iopub.status.busy": "2025-01-21T16:09:34.399884Z",
     "iopub.status.idle": "2025-01-21T16:09:36.341259Z",
     "shell.execute_reply": "2025-01-21T16:09:36.340789Z",
     "shell.execute_reply.started": "2025-01-21T16:09:34.400084Z"
    },
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/usr/local/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n",
      "  from .autonotebook import tqdm as notebook_tqdm\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=10, micro=14, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cu124\n",
      "cuda:0\n"
     ]
    }
   ],
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n",
    "\n",
    "seed = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "1dad802b6500ab83",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.236861Z",
     "start_time": "2025-01-21T15:01:55.363280Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:36.342660Z",
     "iopub.status.busy": "2025-01-21T16:09:36.342268Z",
     "iopub.status.idle": "2025-01-21T16:09:38.189027Z",
     "shell.execute_reply": "2025-01-21T16:09:38.188564Z",
     "shell.execute_reply.started": "2025-01-21T16:09:36.342635Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[(PosixPath('competitions/cifar-10/train/1.png'), 'frog'),\n",
      " (PosixPath('competitions/cifar-10/train/2.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/3.png'), 'truck'),\n",
      " (PosixPath('competitions/cifar-10/train/4.png'), 'deer'),\n",
      " (PosixPath('competitions/cifar-10/train/5.png'), 'automobile')]\n",
      "[(PosixPath('competitions/cifar-10/test/1.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/2.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/3.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/4.png'), 'cat'),\n",
      " (PosixPath('competitions/cifar-10/test/5.png'), 'cat')]\n",
      "50000 300000\n"
     ]
    }
   ],
   "source": [
    "from pathlib import Path\n",
    "\n",
    "# 目的：通过读取csv文件，得到图片的类别，并将图片路径和类别保存到DataFrame中。\n",
    "\n",
    "DATA_DIR1 = Path(\"./\")\n",
    "DATA_DIR2=Path(\"./competitions/cifar-10/\")\n",
    "\n",
    "train_labels_file = DATA_DIR1 / \"trainLabels.csv\"\n",
    "test_csv_file = DATA_DIR1 / \"sampleSubmission.csv\"  # 测试集模板csv文件\n",
    "train_folder = DATA_DIR2 / \"train/\"\n",
    "test_folder = DATA_DIR2 / \"test/\"\n",
    "\n",
    "# 所有的类别\n",
    "class_names = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']\n",
    "\n",
    "\n",
    "# filepath:csv文件路径，folder:图片所在文件夹\n",
    "def parse_csv_file(filepath, folder):\n",
    "    result = []\n",
    "    # 读取所有行\n",
    "    # with 语句：用于管理文件的上下文。在 with 块结束时，文件会自动关闭，无需手动调用 f.close()。\n",
    "    with open(filepath, 'r') as f:\n",
    "        # f.readlines()：读取文件的所有行，返回一个列表，每个元素是一行内容。\n",
    "        # 第一行不需要，因为第一行是标题\n",
    "        lines = f.readlines()[1:]\n",
    "    for line in lines:\n",
    "        # strip('\\n')：去除行末的换行符（\\n）。\n",
    "        # split(',')：按','分割字符串，返回一个列表。\n",
    "        image_id, label_str = line.strip('\\n').split(',')\n",
    "        # 得到图片的路径\n",
    "        image_full_path = folder / f\"{image_id}.png\"\n",
    "        # 得到对应图片的路径和分类\n",
    "        result.append((image_full_path, label_str))\n",
    "    return result\n",
    "\n",
    "\n",
    "# 得到训练集和测试集的图片路径和分类\n",
    "train_labels_info = parse_csv_file(train_labels_file, train_folder)\n",
    "test_csv_info = parse_csv_file(test_csv_file, test_folder)\n",
    "\n",
    "#打印\n",
    "import pprint\n",
    "\n",
    "pprint.pprint(train_labels_info[0:5])\n",
    "pprint.pprint(test_csv_info[0:5])\n",
    "print(len(train_labels_info), len(test_csv_info))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "8cd5063c39c678cf",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.309527Z",
     "start_time": "2025-01-21T15:01:57.237877Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.189793Z",
     "iopub.status.busy": "2025-01-21T16:09:38.189565Z",
     "iopub.status.idle": "2025-01-21T16:09:38.243236Z",
     "shell.execute_reply": "2025-01-21T16:09:38.242779Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.189777Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                            filepath       class\n",
      "0  competitions/cifar-10/train/1.png        frog\n",
      "1  competitions/cifar-10/train/2.png       truck\n",
      "2  competitions/cifar-10/train/3.png       truck\n",
      "3  competitions/cifar-10/train/4.png        deer\n",
      "4  competitions/cifar-10/train/5.png  automobile\n",
      "                                filepath       class\n",
      "0  competitions/cifar-10/train/45001.png       horse\n",
      "1  competitions/cifar-10/train/45002.png  automobile\n",
      "2  competitions/cifar-10/train/45003.png        deer\n",
      "3  competitions/cifar-10/train/45004.png  automobile\n",
      "4  competitions/cifar-10/train/45005.png    airplane\n",
      "                           filepath class\n",
      "0  competitions/cifar-10/test/1.png   cat\n",
      "1  competitions/cifar-10/test/2.png   cat\n",
      "2  competitions/cifar-10/test/3.png   cat\n",
      "3  competitions/cifar-10/test/4.png   cat\n",
      "4  competitions/cifar-10/test/5.png   cat\n"
     ]
    }
   ],
   "source": [
    "# 取前45000张图片作为训练集\n",
    "train_df = pd.DataFrame(train_labels_info[0:45000])\n",
    "# 取后5000张图片作为验证集\n",
    "valid_df = pd.DataFrame(train_labels_info[45000:])\n",
    "# 测试集\n",
    "test_df = pd.DataFrame(test_csv_info)\n",
    "\n",
    "# 为 Pandas DataFrame 的列重新命名\n",
    "train_df.columns = ['filepath', 'class']\n",
    "valid_df.columns = ['filepath', 'class']\n",
    "test_df.columns = ['filepath', 'class']\n",
    "\n",
    "print(train_df.head())\n",
    "print(valid_df.head())\n",
    "print(test_df.head())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "af9444393c2debca",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.317906Z",
     "start_time": "2025-01-21T15:01:57.310547Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.244363Z",
     "iopub.status.busy": "2025-01-21T16:09:38.244089Z",
     "iopub.status.idle": "2025-01-21T16:09:38.938195Z",
     "shell.execute_reply": "2025-01-21T16:09:38.937718Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.244334Z"
    }
   },
   "outputs": [],
   "source": [
    "from PIL import Image\n",
    "from torch.utils.data import Dataset, DataLoader\n",
    "from torchvision import transforms\n",
    "\n",
    "\n",
    "class Cifar10Dataset(Dataset):\n",
    "    df_map = {\n",
    "        \"train\": train_df,\n",
    "        \"eval\": valid_df,\n",
    "        \"test\": test_df\n",
    "    }\n",
    "    # enumerate(class_names)：遍历 class_names，返回索引和类别名称的元组\n",
    "    # 将类别名称映射为索引，通常用于将标签转换为模型可以处理的数值。\n",
    "    label_to_idx = {\n",
    "        label: idx for idx, label in enumerate(class_names)\n",
    "    }\n",
    "    # 将索引映射为类别名称，通常用于将模型的输出（索引）转换回可读的类别名称\n",
    "    idx_to_label = {\n",
    "        idx: label for idx, label in enumerate(class_names)\n",
    "    }\n",
    "\n",
    "    def __init__(self, mode, transform=None):\n",
    "        # 获取对应模式的df，不同字符串对应不同模式\n",
    "        self.df = self.df_map.get(mode, None)\n",
    "        if self.df is None:\n",
    "            raise ValueError(f\"Invalid mode: {mode}\")\n",
    "        self.transform = transform\n",
    "\n",
    "    def __getitem__(self, index):\n",
    "        # 获取图片路径和标签\n",
    "        img_path, label = self.df.iloc[index]\n",
    "        # 打开一张图片并将其转换为 RGB 格式\n",
    "        img = Image.open(img_path).convert('RGB')  # 确保图片具有三个通道\n",
    "        # 应用数据增强\n",
    "        img = self.transform(img)\n",
    "        # label 转换为 idx\n",
    "        label = self.label_to_idx[label]\n",
    "        return img, label\n",
    "\n",
    "    def __len__(self):\n",
    "        # 返回df的行数,样本数\n",
    "        return self.df.shape[0]\n",
    "\n",
    "\n",
    "IMAGE_SIZE = 32\n",
    "mean, std = [0.4914, 0.4822, 0.4465], [0.247, 0.243, 0.261]\n",
    "\n",
    "#数据增强\n",
    "# ToTensor还将图像的维度从[height, width, channels]转换为[channels, height, width]。\n",
    "transforms_train = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),  # 缩放\n",
    "    transforms.RandomRotation(40),  # 随机旋转\n",
    "    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n",
    "    transforms.ToTensor(),  # 转换为Tensor\n",
    "    transforms.Normalize(mean, std)  # 标准化\n",
    "])\n",
    "\n",
    "transforms_eval = transforms.Compose([\n",
    "    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize(mean, std)\n",
    "])\n",
    "\n",
    "train_ds = Cifar10Dataset(mode=\"train\", transform=transforms_train)\n",
    "eval_ds = Cifar10Dataset(mode=\"eval\", transform=transforms_eval)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d17ef4a56f0b6ae1",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.325416Z",
     "start_time": "2025-01-21T15:01:57.319918Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.939051Z",
     "iopub.status.busy": "2025-01-21T16:09:38.938730Z",
     "iopub.status.idle": "2025-01-21T16:09:38.946720Z",
     "shell.execute_reply": "2025-01-21T16:09:38.946331Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.939033Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "torch.Size([3, 32, 32])"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_ds[0][0].shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "161282a4e612afcd",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.330889Z",
     "start_time": "2025-01-21T15:01:57.326424Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.947521Z",
     "iopub.status.busy": "2025-01-21T16:09:38.947175Z",
     "iopub.status.idle": "2025-01-21T16:09:38.950010Z",
     "shell.execute_reply": "2025-01-21T16:09:38.949585Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.947504Z"
    }
   },
   "outputs": [],
   "source": [
    "batch_size = 64\n",
    "\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size,\n",
    "                          shuffle=True)\n",
    "eval_loader = DataLoader(eval_ds, batch_size=batch_size,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20b5b31416450980",
   "metadata": {},
   "source": [
    "## 模型定义"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "e439ac9c11ab0e5a",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.338745Z",
     "start_time": "2025-01-21T15:01:57.331893Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.951664Z",
     "iopub.status.busy": "2025-01-21T16:09:38.951347Z",
     "iopub.status.idle": "2025-01-21T16:09:38.956891Z",
     "shell.execute_reply": "2025-01-21T16:09:38.956484Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.951649Z"
    }
   },
   "outputs": [],
   "source": [
    "class InceptionBlock(nn.Module):\n",
    "    # output_channel_for_each_path:\n",
    "    # 1*1卷积输出通道数，3*3卷积输出通道数，5*5卷积输出通道数，池化层输出通道数\n",
    "    def __init__(self, input_channels: int,\n",
    "                 output_channel_for_each_path: list[int]):\n",
    "        super().__init__()\n",
    "        self.conv1_1 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channel_for_each_path[0],\n",
    "            kernel_size=1,\n",
    "            padding=\"same\"\n",
    "        )\n",
    "        self.conv3_3 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channel_for_each_path[1],\n",
    "            kernel_size=3,\n",
    "            padding=\"same\"\n",
    "        )\n",
    "        self.conv5_5 = nn.Conv2d(\n",
    "            in_channels=input_channels,\n",
    "            out_channels=output_channel_for_each_path[2],\n",
    "            kernel_size=5,\n",
    "            padding=\"same\"\n",
    "        )\n",
    "        self.max_pool = nn.MaxPool2d(kernel_size=2, stride=2)\n",
    "\n",
    "    def forward(self, x):\n",
    "        conv1_1 = F.relu(self.conv1_1(x))\n",
    "        conv3_3 = F.relu(self.conv3_3(x))\n",
    "        conv5_5 = F.relu(self.conv5_5(x))\n",
    "        max_pool = self.max_pool(x)  # 最大池化，图像尺寸缩小一半\n",
    "\n",
    "        max_pool_shape = max_pool.shape[1:]  #最后3个维度，即通道、高、宽\n",
    "        input_shape = x.shape[1:]  #最后3个维度，即通道、高、宽\n",
    "        # 输入尺寸，减去池化尺寸，再除以2\n",
    "        width_padding = (input_shape[-2] - max_pool_shape[-2]) // 2\n",
    "        height_padding = (input_shape[-1] - max_pool_shape[-1]) // 2\n",
    "\n",
    "        padded_pool = F.pad(\n",
    "            max_pool,\n",
    "            [width_padding, width_padding, height_padding, height_padding]\n",
    "            # [left, right, top, bottom]，\n",
    "            # 在每个维度上填充的长度，默认填充最后两个维度，即高和宽，填充0\n",
    "        )\n",
    "\n",
    "        concat_output = torch.cat(\n",
    "            [conv1_1, conv3_3, conv5_5, padded_pool],\n",
    "            dim=1  # 在通道维度上拼接\n",
    "        )\n",
    "        return concat_output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "e7110efbcf5efc1b",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.345733Z",
     "start_time": "2025-01-21T15:01:57.339748Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.957434Z",
     "iopub.status.busy": "2025-01-21T16:09:38.957294Z",
     "iopub.status.idle": "2025-01-21T16:09:38.962455Z",
     "shell.execute_reply": "2025-01-21T16:09:38.961853Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.957420Z"
    }
   },
   "outputs": [],
   "source": [
    "class InceptionNet(nn.Module):\n",
    "    def __init__(self, num_classes=10):\n",
    "        super(InceptionNet, self).__init__()\n",
    "        self.model = nn.Sequential(\n",
    "            nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, stride=1, padding=\"same\"), # 32*32*32\n",
    "            nn.ReLU(),\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#32*16*16\n",
    "            InceptionBlock(input_channels=32, output_channel_for_each_path=[16, 16, 16]),# (16+16+16+32)*16*16->80*16*16\n",
    "            InceptionBlock(input_channels=80, output_channel_for_each_path=[16, 16, 16]), #(16+16+16+80)*16*16->128*16*16\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#128*8*8\n",
    "            InceptionBlock(input_channels=128, output_channel_for_each_path=\n",
    "            [16, 16, 16]), # (16+16+16+128)*8*8->176*8*8          \n",
    "            InceptionBlock(input_channels=176, output_channel_for_each_path=\n",
    "            [16, 16, 16]), # (16+16+16+176)*8*8->224*8*8\n",
    "            nn.MaxPool2d(kernel_size=2, stride=2),#224*4*4\n",
    "            nn.Flatten(),# 224*4*4=3584\n",
    "            nn.Linear(3584, num_classes)\n",
    "        )\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层、卷积层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, (nn.Linear, nn.Conv2d)):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "                \n",
    "    def forward(self, x):\n",
    "        return self.model(x)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "bb519b4756d62090",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.355344Z",
     "start_time": "2025-01-21T15:01:57.346738Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.963242Z",
     "iopub.status.busy": "2025-01-21T16:09:38.962946Z",
     "iopub.status.idle": "2025-01-21T16:09:38.970960Z",
     "shell.execute_reply": "2025-01-21T16:09:38.970576Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.963228Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "             model.0.weight             paramerters num: 864\n",
      "              model.0.bias              paramerters num: 32\n",
      "         model.3.conv1_1.weight         paramerters num: 512\n",
      "          model.3.conv1_1.bias          paramerters num: 16\n",
      "         model.3.conv3_3.weight         paramerters num: 4608\n",
      "          model.3.conv3_3.bias          paramerters num: 16\n",
      "         model.3.conv5_5.weight         paramerters num: 12800\n",
      "          model.3.conv5_5.bias          paramerters num: 16\n",
      "         model.4.conv1_1.weight         paramerters num: 1280\n",
      "          model.4.conv1_1.bias          paramerters num: 16\n",
      "         model.4.conv3_3.weight         paramerters num: 11520\n",
      "          model.4.conv3_3.bias          paramerters num: 16\n",
      "         model.4.conv5_5.weight         paramerters num: 32000\n",
      "          model.4.conv5_5.bias          paramerters num: 16\n",
      "         model.6.conv1_1.weight         paramerters num: 2048\n",
      "          model.6.conv1_1.bias          paramerters num: 16\n",
      "         model.6.conv3_3.weight         paramerters num: 18432\n",
      "          model.6.conv3_3.bias          paramerters num: 16\n",
      "         model.6.conv5_5.weight         paramerters num: 51200\n",
      "          model.6.conv5_5.bias          paramerters num: 16\n",
      "         model.7.conv1_1.weight         paramerters num: 2816\n",
      "          model.7.conv1_1.bias          paramerters num: 16\n",
      "         model.7.conv3_3.weight         paramerters num: 25344\n",
      "          model.7.conv3_3.bias          paramerters num: 16\n",
      "         model.7.conv5_5.weight         paramerters num: 70400\n",
      "          model.7.conv5_5.bias          paramerters num: 16\n",
      "            model.10.weight             paramerters num: 35840\n",
      "             model.10.bias              paramerters num: 10\n"
     ]
    }
   ],
   "source": [
    "for key, value in InceptionNet(len(class_names)).named_parameters():\n",
    "    print(f\"{key:^40}paramerters num: {np.prod(value.shape)}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "b294d25e7f6287d0",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.364691Z",
     "start_time": "2025-01-21T15:01:57.356346Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.971703Z",
     "iopub.status.busy": "2025-01-21T16:09:38.971383Z",
     "iopub.status.idle": "2025-01-21T16:09:38.978535Z",
     "shell.execute_reply": "2025-01-21T16:09:38.978154Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.971688Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total params: 269,898\n"
     ]
    }
   ],
   "source": [
    "#计算总参数量\n",
    "total_params = sum(p.numel() for p in InceptionNet(len(class_names)).parameters() if p.requires_grad)\n",
    "print(f\"Total params: {total_params:,}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "76ca49d0dca74376",
   "metadata": {},
   "source": [
    "## 模型训练"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "632d31ce33563b0c",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.424672Z",
     "start_time": "2025-01-21T15:01:57.366697Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:38.979224Z",
     "iopub.status.busy": "2025-01-21T16:09:38.978973Z",
     "iopub.status.idle": "2025-01-21T16:09:39.002806Z",
     "shell.execute_reply": "2025-01-21T16:09:39.002366Z",
     "shell.execute_reply.started": "2025-01-21T16:09:38.979209Z"
    }
   },
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "28c2ba49f4ff546",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.430748Z",
     "start_time": "2025-01-21T15:01:57.425689Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:39.003690Z",
     "iopub.status.busy": "2025-01-21T16:09:39.003288Z",
     "iopub.status.idle": "2025-01-21T16:09:39.007770Z",
     "shell.execute_reply": "2025-01-21T16:09:39.007357Z",
     "shell.execute_reply.started": "2025-01-21T16:09:39.003673Z"
    }
   },
   "outputs": [],
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"09_inception_net_best.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cd4d61586ad24eb5",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.437008Z",
     "start_time": "2025-01-21T15:01:57.431753Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:39.008536Z",
     "iopub.status.busy": "2025-01-21T16:09:39.008373Z",
     "iopub.status.idle": "2025-01-21T16:09:39.011890Z",
     "shell.execute_reply": "2025-01-21T16:09:39.011488Z",
     "shell.execute_reply.started": "2025-01-21T16:09:39.008521Z"
    }
   },
   "outputs": [],
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "725cf8a521379801",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.444930Z",
     "start_time": "2025-01-21T15:01:57.438011Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:39.012682Z",
     "iopub.status.busy": "2025-01-21T16:09:39.012426Z",
     "iopub.status.idle": "2025-01-21T16:09:39.019076Z",
     "shell.execute_reply": "2025-01-21T16:09:39.018626Z",
     "shell.execute_reply.started": "2025-01-21T16:09:39.012666Z"
    }
   },
   "outputs": [],
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             val_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "22bf1e82c9715088",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:01:57.455775Z",
     "start_time": "2025-01-21T15:01:57.445935Z"
    },
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:39.019724Z",
     "iopub.status.busy": "2025-01-21T16:09:39.019575Z",
     "iopub.status.idle": "2025-01-21T16:09:39.028049Z",
     "shell.execute_reply": "2025-01-21T16:09:39.027676Z",
     "shell.execute_reply.started": "2025-01-21T16:09:39.019710Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "epoch = 100\n",
    "\n",
    "model = InceptionNet(len(class_names))  # 定义模型\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "\n",
    "# 2. 定义优化器 采用 adam\n",
    "optimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "if not os.path.exists(\"checkpoints\"):\n",
    "    os.makedirs(\"checkpoints\")\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=len(train_loader), save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=5, min_delta=0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "5335f62fe5e4977f",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:02:13.415330Z",
     "start_time": "2025-01-21T15:01:57.456778Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:09:39.028783Z",
     "iopub.status.busy": "2025-01-21T16:09:39.028478Z",
     "iopub.status.idle": "2025-01-21T16:17:50.788953Z",
     "shell.execute_reply": "2025-01-21T16:17:50.788510Z",
     "shell.execute_reply.started": "2025-01-21T16:09:39.028768Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      " 19%|█▉        | 13376/70400 [08:11<34:56, 27.20it/s, epoch=18]  "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 19 / global_step 13376\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    }
   ],
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    eval_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1a790e5f4635d5e8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2025-01-21T16:17:50.789657Z",
     "iopub.status.busy": "2025-01-21T16:17:50.789497Z",
     "iopub.status.idle": "2025-01-21T16:17:50.931846Z",
     "shell.execute_reply": "2025-01-21T16:17:50.931429Z",
     "shell.execute_reply.started": "2025-01-21T16:17:50.789641Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def plot_record_curves(record_dict, sample_step=500):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    # print(train_df)\n",
    "    # print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1] + 1, 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d9729d15a0d01e8",
   "metadata": {},
   "source": [
    "## 评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "3195e10a60c29806",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-21T15:02:13.418335Z",
     "start_time": "2025-01-21T15:02:13.418335Z"
    },
    "execution": {
     "iopub.execute_input": "2025-01-21T16:17:50.932562Z",
     "iopub.status.busy": "2025-01-21T16:17:50.932397Z",
     "iopub.status.idle": "2025-01-21T16:17:52.752325Z",
     "shell.execute_reply": "2025-01-21T16:17:52.751886Z",
     "shell.execute_reply.started": "2025-01-21T16:17:50.932547Z"
    },
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.7267, Test acc: 0.7576\n"
     ]
    }
   ],
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/09_inception_net_best.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, eval_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.14"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
